package com.jprocms.module.cms.service.content;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.page.PageMethod;
import com.jpro.framework.common.exception.ErrorCode;
import com.jpro.framework.common.pojo.CommonResult;
import com.jpro.framework.common.pojo.PageParam;
import com.jpro.framework.common.pojo.PageResult;
import com.jpro.framework.common.util.collection.CollectionUtils;
import com.jpro.framework.common.util.string.StrUtils;
import com.jpro.framework.security.core.util.SecurityFrameworkUtils;
import com.jpro.framework.tenant.core.context.TenantContextHolder;
import com.jpro.module.bpm.api.task.BpmProcessInstanceApi;
import com.jpro.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import com.jpro.module.bpm.api.task.enums.ApiConstants;
import com.jpro.module.bpm.api.task.rpc.BpmTaskApi;
import com.jpro.module.bpm.api.task.vo.BpmTaskApproveReqVO;
import com.jpro.module.bpm.api.task.vo.BpmTaskRejectReqVO;
import com.jpro.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import com.jpro.module.system.api.dept.DeptApi;
import com.jpro.module.system.api.permission.dto.ContentPermissionRespDTO;
import com.jpro.module.system.api.permission.dto.DeptDataPermissionRespDTO;
import com.jpro.module.system.api.sensitiveword.SensitiveWordApi;
import com.jpro.module.system.api.user.AdminUserApi;
import com.jpro.module.system.api.user.dto.AdminUserRespDTO;
import com.jprocms.module.cms.content.ContentEventPublisher;
import com.jprocms.module.cms.controller.admin.content.dto.ContentAutoPushDto;
import com.jprocms.module.cms.controller.admin.content.vo.*;
import com.jprocms.module.cms.convert.PageConverter;
import com.jprocms.module.cms.convert.content.ContentAttrConvert;
import com.jprocms.module.cms.convert.content.ContentConvert;
import com.jprocms.module.cms.convert.content.ContentExtConvert;
import com.jprocms.module.cms.dal.dataobject.channel.ChannelDO;
import com.jprocms.module.cms.dal.dataobject.content.*;
import com.jprocms.module.cms.dal.dataobject.model.ModelDO;
import com.jprocms.module.cms.dal.dataobject.site.SiteDO;
import com.jprocms.module.cms.dal.mysql.channel.ChannelMapper;
import com.jprocms.module.cms.dal.mysql.content.*;
import com.jprocms.module.cms.dal.mysql.model.ModelMapper;
import com.jprocms.module.cms.enums.EnumContentPushType;
import com.jprocms.module.cms.enums.EnumContentStatus;
import com.jprocms.module.cms.enums.EnumCreateMode;
import com.jprocms.module.cms.enums.EnumIntCommonStatus;
import com.jprocms.module.cms.framework.enums.EnumContentEvent;
import com.jprocms.module.cms.framework.util.AssertUtil;
import com.jprocms.module.cms.job.ContentJobService;
import com.jprocms.module.cms.permission.PermissionServiceImpl;
import com.jprocms.module.cms.service.globalconfig.GlobalConfigService;
import com.jprocms.module.cms.service.site.SiteService;
import com.jprocms.module.cms.util.CmsContextUtil;
import com.jprocms.module.cms.util.EnvUtil;
import com.jprocms.module.cms.util.HtmlParserUtil;
import com.jprocms.module.cms.util.RequestUtils;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.jpro.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.jprocms.module.cms.constant.CmsSysConstants.CMS_TENANT_ID;
import static com.jprocms.module.cms.dal.dataobject.content.ContentDO.REQUEST_ATTR_CONTENT_ORIGIN_CHANNEL_ID;
import static com.jprocms.module.cms.dal.dataobject.content.ContentDO.REQUEST_ATTR_CONTENT_ORIGIN_CHANNEL_ID_MAP;
import static com.jprocms.module.cms.enums.ErrorCodeConstants.*;
import static com.jprocms.module.cms.enums.LogRecordConstants.*;
import static com.jprocms.module.cms.util.Freemarkers.ORDER_TABLE_CONTENT_COUNT;
import static com.jprocms.module.cms.web.Directives.getOrderItems;

/**
 * 内容 Service 实现类
 *
 * @author jprocms
 */
@Slf4j
@ConditionalOnProperty(name = "jpro.cms.contentQuerySource", havingValue = "db", matchIfMissing = true)
@Service
@Validated
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentDO> implements ContentService {

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_CREATE_SUB_TYPE, bizNo = "{{#content.id}}", success = CMS_CONTENT_CREATE_SUCCESS, condition = "{{!#createReqVO.livePreview}}")
    @Transactional(rollbackFor = Exception.class)
    public Long createContent(ContentCreateReqVO createReqVO) {
        //检查是否有敏感词 看系统配置配置策略是否不允许提交还是可以替换
        createReqVO = (ContentCreateReqVO) sensitiveWordService.checkAndProcessSensitiveWord(createReqVO);
        //如果有预约发布上线时间是将来时间则处理为预约发布状态
        final Date onlineTime = createReqVO.getOnlineTime();
        if (onlineTime != null && onlineTime.after(Calendar.getInstance().getTime())) {
            createReqVO.setStatus(EnumContentStatus.AUDIT_FINISH.getStatus());
        }
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        createReqVO.setUserId(userId);
        ContentDO content = ContentConvert.INSTANCE.convert(createReqVO);
        content.init();
        AdminUserRespDTO adminUserRespDTO = adminUserApi.getUser(userId).getCheckedData();
        if (adminUserRespDTO != null) {
            // 找不到部门,如果有部分数据范围 则这条数据会被忽略
            content.setDeptId(adminUserRespDTO.getDeptId());
        }
        // 如果不为草稿，也不是待发布 就设置为已发布。
        if (EnumContentStatus.DRAFT.equals(createReqVO.getStatus())
                || EnumContentStatus.AUDIT_FINISH.equals(createReqVO.getStatus())) {
            createReqVO.setStatus(EnumContentStatus.PUBLISHED.getStatus());
        }
        Long channelId = createReqVO.getChannelId();
        ChannelDO channelDO = channelMapper.selectById(channelId);
        Assert.notNull(channelDO, () -> {
            throw exception(CHANNEL_NOT_EXISTS);
        });

        content.setModelId(channelDO.getContentModelId());
        contentMapper.insert(content);
        long contentId = content.getId();
        //系统默认正文处理下载的图片
        if (createReqVO.getDownRemoteImage() != null && createReqVO.getDownRemoteImage() && StringUtils.isNotBlank(createReqVO.getText())) {
            createReqVO.setText(HtmlParserUtil.downloadImageReplace(createReqVO.getText()));
        }
        //ext表
        ContentExtDO ext = ContentExtConvert.INSTANCE.convert(createReqVO);
        ext.setId(content.getId());
        ext.init();
        content.setExt(ext);
        contentExtMapper.insert(ext);
        //自定义属性
        Map<String, Object> customs = createReqVO.getCustoms();
        saveCustoms(content, customs, createReqVO.getDownRemoteImage());
        //先保存tag词后再保存到内容表
        tagService.saveTags(contentId, createReqVO.getTagNames());
        //保存引用栏目信息
        saveContentSubChannels(contentId, createReqVO.getSubChannelIds());
        //计数表
        contentCountMapper.insert(new ContentCountDO(contentId));

        String processDefinitionId = channelDO.getUseProcessDefinitionId();
        //非预览创建 如果目标状态是发布状态且栏目有工作流则提交审核
        if (!createReqVO.getLivePreview()
                && EnumContentStatus.PUBLISHED.getStatus().equals(content.getStatus())
                && StringUtils.isNotBlank(processDefinitionId)) {
            submit(new ContentSubmitVO(contentId, ""));
        }
        //创建内容发布 置顶任务
        contentJobService.createScheduleTask(content);
        //发送创建成功消息 同步栏目自动采集规则 同步ES 同步生成静态页
        contentEventPublisher.sendContentCreateUpdateEvent(contentId, EnumContentEvent.create);

        //日志上下文
        LogRecordContext.putVariable("content", content);
        return contentId;
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", success = CMS_CONTENT_UPDATE_SUCCESS)
    @Transactional(rollbackFor = Exception.class)
    public void updateContent(ContentUpdateReqVO updateReqVO) {
        // 校验存在
        final ContentDO contentDO = validateContentExists(updateReqVO.getId());
        //如果有预约发布上线时间是将来时间则处理为预约发布状态
        final Date onlineTime = updateReqVO.getOnlineTime();
        if (onlineTime != null && onlineTime.after(Calendar.getInstance().getTime())) {
            updateReqVO.setStatus(EnumContentStatus.AUDIT_FINISH.getStatus());
        }
        final ContentUpdateReqVO contentOld = ContentConvert.INSTANCE.convertToUpdateReqVO(contentDO);
        //检查是否有敏感词 看系统配置配置策略是否不允许提交还是可以替换
        updateReqVO = (ContentUpdateReqVO) sensitiveWordService.checkAndProcessSensitiveWord(updateReqVO);

        final Long originChannelId = contentDO.getChannelId();
        // 更新
        ContentDO content = ContentConvert.INSTANCE.convert(updateReqVO);
        //先保存tag词后再保存到内容表
        tagService.saveTags(content.getId(), updateReqVO.getTagNames());
        //保存引用栏目信息
        saveContentSubChannels(content.getId(), updateReqVO.getSubChannelIds());
        contentMapper.updateById(content);
        //系统默认正文处理下载的图片
        if (updateReqVO.getDownRemoteImage() != null && updateReqVO.getDownRemoteImage() && StringUtils.isNotBlank(updateReqVO.getText())) {
            updateReqVO.setText(HtmlParserUtil.downloadImageReplace(updateReqVO.getText()));
        }
        ContentExtDO ext = ContentExtConvert.INSTANCE.convert(updateReqVO);
        ext.setId(updateReqVO.getId());
        ext.init();
        contentExtMapper.updateById(ext);
        //自定义属性
        //刪除原有 重新保存
        contentAttrService.deleteByContentId(updateReqVO.getId());
        Map<String, Object> customs = updateReqVO.getCustoms();
        saveCustoms(content, customs, updateReqVO.getDownRemoteImage());
        content.setExt(ext);
        //创建内容发布 置顶任务
        contentJobService.createScheduleTask(content);
        //发送修改消息 如果开启站点静态化则生成静态页 同步ES
        RequestUtils.getHttpServletRequest().setAttribute(REQUEST_ATTR_CONTENT_ORIGIN_CHANNEL_ID, originChannelId);
        contentEventPublisher.sendContentCreateUpdateEvent(updateReqVO.getId(), EnumContentEvent.update);
        // 记录操作日志上下文
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, contentOld);
        LogRecordContext.putVariable("content", content);
    }


    private void saveCustoms(ContentDO content, Map<String, Object> customs, Boolean downRemoteImage) {
        if (!customs.isEmpty()) {
            ChannelDO channelDO = channelMapper.selectById(content.getChannelId());
            ModelDO modelDO = modelMapper.selectById(channelDO.getContentModelId());
            List<ContentAttrDO> customDos = ContentAttrConvert.INSTANCE.disassembleCustoms(modelDO, content.getId(), customs);
            //处理富文本里面需要下载的图片替换
            if (downRemoteImage != null && downRemoteImage) {
                for (ContentAttrDO customDo : customDos) {
                    if (customDo.isRichEditor()) {
                        String attrValue = customDo.getAttrValue();
                        if (StringUtils.isNotBlank(attrValue)) {
                            attrValue = HtmlParserUtil.downloadImageReplace(attrValue);
                            customDo.setAttrValue(attrValue);
                        }
                    }
                }
            }
            contentAttrMapper.insertBatch(customDos);
        }
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_DELETE_SUB_TYPE, bizNo = "{{#id}}", success = CMS_CONTENT_DELETE_SUCCESS)
    @Transactional(rollbackFor = Exception.class)
    public void deleteContent(Long id) {
        // 校验存在
        ContentDO contentDO = validateContentExists(id);
        contentDO.setStatus(EnumContentStatus.DEL.getStatus());
        contentMapper.updateById(contentDO);
        //发送删除消息 索引  重新生成的静态页
        contentEventPublisher.sendContentDeleteEvent(Arrays.asList(id));
        LogRecordContext.putVariable("content", contentDO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteTwoContent(Long id) {
        // 校验存在
        ContentDO contentDO = validateContentExists(id);
        contentDO.setStatus(EnumContentStatus.DEL_TWO.getStatus());
        contentMapper.updateById(contentDO);
        // 删除
        contentMapper.deleteById(id);
        contentExtMapper.deleteById(id);
        contentCountMapper.deleteById(id);
        //刪除attr
        contentAttrService.deleteByContentId(id);
        //有可能直接批量删除，发送删除消息
        contentEventPublisher.sendContentDeleteEvent(Arrays.asList(id));
        LogRecordContext.putVariable("content", contentDO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_CHANNEL_DELETE_SUB_TYPE, bizNo = "{{#channelId}}", success = CMS_CONTENT_CHANNEL_DELETE_SUCCESS)
    public void deleteContentsByChannelId(Long channelId, boolean toRecycle) {
        LambdaUpdateWrapper<ContentDO> wrapper = new LambdaUpdateWrapper<>();
        if (toRecycle) {
            wrapper.set(ContentDO::getStatus, EnumContentStatus.DEL.getStatus());
            //删除内容其他索引 静态页
            LambdaQueryWrapper<ContentDO> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.select(ContentDO::getId).eq(ContentDO::getChannelId, channelId).eq(ContentDO::getDeleted, 0);
            final List<Long> contentIds = contentMapper.selectObjs(lambdaQueryWrapper).stream().map(o -> (Long) o).collect(Collectors.toList());
            if (CollUtil.isNotEmpty(contentIds)) {
                contentEventPublisher.sendContentDeleteEvent(contentIds);
            }
        } else {
            wrapper.set(ContentDO::getStatus, EnumContentStatus.DEL_TWO.getStatus());
        }
        wrapper.in(ContentDO::getChannelId, channelId);
        LogRecordContext.putVariable("channelId", channelId);
        LogRecordContext.putVariable("toRecycle", toRecycle);
        super.update(new ContentDO(), wrapper);
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_CHANNEL_STATUS_SUB_TYPE, bizNo = "{{#channelId}}", success = CMS_CONTENT_CHANNEL_STATUS_SUCCESS)
    @Transactional(rollbackFor = Exception.class)
    public void updateContentsStatusByChannelId(Long channelId, Integer status) {
        LambdaUpdateWrapper<ContentDO> wrapper = new LambdaUpdateWrapper<>();
        wrapper.set(ContentDO::getStatus, status);
        wrapper.in(ContentDO::getChannelId, channelId);
        LogRecordContext.putVariable("channelId", channelId);
        LogRecordContext.putVariable("status", status);
        super.update(new ContentDO(), wrapper);
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_SUBMIT_SUB_TYPE, bizNo = "{{#submitVO.id}}", success = CMS_CONTENT_SUBMIT_SUCCESS)
    @Transactional(rollbackFor = Exception.class)
    public void submit(ContentSubmitVO submitVO) {
        ContentExtDO contentExt = contentExtMapper.selectById(submitVO.getId());
        ContentDO content = getContent(submitVO.getId());
        Assert.notNull(contentExt, () -> {
            throw exception(CONTENT_NOT_EXISTS);
        });
        Assert.notNull(content, () -> {
            throw exception(CONTENT_NOT_EXISTS);
        });
        final String useProcessDefinitionId = content.getChannel().getUseProcessDefinitionId();
        Assert.notNull(useProcessDefinitionId, () -> {
            throw exception(CONTENT_WORKFLOW_NOT_EXISTS);
        });
        // 发起 BPM 流程
        Map<String, Object> processInstanceVariables = new HashMap<>();
        //查询栏目设置工作流流程模型
        processInstanceVariables.put(ApiConstants.BPM_FORMVARIABLES_CONTENTID, submitVO.getId());

        final String processDefinitionKey = useProcessDefinitionId.substring(0, useProcessDefinitionId.indexOf(":"));
        BpmProcessInstanceCreateReqDTO bpmProcessInstanceCreateReqDTO = new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(processDefinitionKey)
                .setVariables(processInstanceVariables).setProcessDefinitionId(useProcessDefinitionId).setBusinessKey(submitVO.getId().toString())
                .setStartUserSelectAssignees(submitVO.getStartUserSelectAssignees());

        CommonResult<String> createProcessInstanceRes = processInstanceApi.createProcessInstance(SecurityFrameworkUtils.getLoginUserId(), bpmProcessInstanceCreateReqDTO);

        if (createProcessInstanceRes.isError()) {
            throw exception(new ErrorCode(createProcessInstanceRes.getCode(), createProcessInstanceRes.getMsg()));
        }
        String processInstanceId = createProcessInstanceRes.getData();
        // 将工作流的编号，更新到 内容表中 并更新状态
        contentExt.setProcessInstanceId(processInstanceId);
        contentExtMapper.updateById(contentExt);
        content.setStatus(EnumContentStatus.AUDITING.getStatus());
        contentMapper.updateById(content);
        //发布状态变更 触发相关事件
        //比如下线已经发布的文章 索引 删除已经生成的静态页
        contentEventPublisher.sendContentCreateUpdateEvent(submitVO.getId(), EnumContentEvent.update);
        LogRecordContext.putVariable("content", content);
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_APPROVE_SUB_TYPE, bizNo = "{{#reqVO.id}}", success = CMS_CONTENT_APPROVE_SUCCESS)
    @Transactional(rollbackFor = Exception.class)
    public void approve(ContentTaskApproveReqVO reqVO) {
        ContentDO content = getContent(reqVO.getId());
        Assert.notNull(content, () -> {
            throw exception(CONTENT_NOT_EXISTS);
        });
        BpmTaskApproveReqVO bpmTaskApproveReqVO = new BpmTaskApproveReqVO(reqVO.getTaskId(), reqVO.getReason(), reqVO.getCopyUserIds(), reqVO.getVariables());
        CommonResult<Boolean> bpmTaskResult = bpmTaskApi.approveTask(bpmTaskApproveReqVO);
        if (bpmTaskResult.isError()) {
            throw exception(new ErrorCode(bpmTaskResult.getCode(), bpmTaskResult.getMsg()));
        }
        LogRecordContext.putVariable("content", content);
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_REJECT_SUB_TYPE, bizNo = "{{#reqVO.id}}", success = CMS_CONTENT_REJECT_SUCCESS)
    @Transactional(rollbackFor = Exception.class)
    public void reject(ContentTaskRejectReqVO reqVO) {
        ContentDO content = getContent(reqVO.getId());
        Assert.notNull(content, () -> {
            throw exception(CONTENT_NOT_EXISTS);
        });
        BpmTaskRejectReqVO bpmTaskRejectReqVO = new BpmTaskRejectReqVO(reqVO.getTaskId(), reqVO.getReason());
        CommonResult<Boolean> bpmTaskResult = bpmTaskApi.rejectTask(bpmTaskRejectReqVO);
        if (bpmTaskResult.isError()) {
            throw exception(new ErrorCode(bpmTaskResult.getCode(), bpmTaskResult.getMsg()));
        }
        LogRecordContext.putVariable("content", content);
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_AUDIT_SUB_TYPE, bizNo = "{{#id}}", success = CMS_CONTENT_AUDIT_SUCCESS)
    public void updateAuditResult(Long id, Integer result) {
        Assert.notNull(id, () -> {
            throw exception(CONTENT_NOT_EXISTS);
        });
        ContentDO content = getContent(id);
        Assert.notNull(content, () -> {
            throw exception(CONTENT_NOT_EXISTS);
        });
        if (BpmProcessInstanceResultEnum.APPROVE.getResult().equals(result)) {
            //审核后自动发布
            content.setStatus(EnumContentStatus.PUBLISHED.getStatus());
        } else if (BpmProcessInstanceResultEnum.REJECT.getResult().equals(result) || BpmProcessInstanceResultEnum.BACK.getResult().equals(result)) {
            //不通过或者驳回 到退回状态
            content.setStatus(EnumContentStatus.AUDIT_FAILED.getStatus());
        }
        if (BpmProcessInstanceResultEnum.CANCEL.getResult().equals(result)) {
            //取消回到初稿
            content.setStatus(EnumContentStatus.DRAFT.getStatus());
        }
        //流程处理中忽略
        contentMapper.updateById(content);
        //发布状态变更 触发相关事件
        // 比如状态已经发布 索引  重新生成的静态页
        contentEventPublisher.sendContentCreateUpdateEvent(id, EnumContentEvent.update);
        LogRecordContext.putVariable("content", content);
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_DELETE_SUB_TYPE, bizNo = "{{#ids[0]}}", success = CMS_CONTENT_DELETE_BATCH_SUCCESS)
    @Transactional(rollbackFor = Exception.class)
    public void batchDeleteContent(List<Long> ids) {
        for (Long id : ids) {
            deleteContent(id);
        }
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_TWO_DELETE_SUB_TYPE, bizNo = "{{#ids[0]}}", success = CMS_CONTENT_TWO_DELETE_BATCH_SUCCESS)
    @Transactional(rollbackFor = Exception.class)
    public void batchTwoDeleteContent(List<Long> ids) {
        for (Long id : ids) {
            deleteTwoContent(id);
        }
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_ARCHIVE_SUB_TYPE, bizNo = "{{#id}}", success = CMS_CONTENT_ARCHIVE_SUCCESS)
    @Transactional(rollbackFor = Exception.class)
    public void archiveContent(Long id) {
        // 校验存在
        ContentDO contentDO = validateContentExists(id);
        //只处理已发布的内容
        if (EnumContentStatus.PUBLISHED.getStatus().equals(contentDO.getStatus())) {
            contentDO.setStatus(EnumContentStatus.ARCHIVED.getStatus());
            contentMapper.updateById(contentDO);
            // 发布状态变更 触发相关事件 比如状态已经发布 索引  重新生成的静态页
            contentEventPublisher.sendContentCreateUpdateEvent(id, EnumContentEvent.update);
        }
        LogRecordContext.putVariable("content", contentDO);
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_ARCHIVE_SUB_TYPE, bizNo = "{{#ids[0]}}", success = CMS_CONTENT_ARCHIVE_BATCH_SUCCESS, extra = "{{#ids}}")
    @Transactional(rollbackFor = Exception.class)
    public void batchArchiveContent(List<Long> ids) {
        for (Long id : ids) {
            archiveContent(id);
        }
    }


    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_TOP_SUB_TYPE, bizNo = "{{#topVo.ids[0]}}", success = CMS_CONTENT_TOP_BATCH_SUCCESS, extra = "{{#topVo.ids}}")
    @Transactional(rollbackFor = Exception.class)
    public void batchTopContent(ContentTopVo topVo) {
        for (Long id : topVo.getIds()) {
            // 校验存在
            ContentDO contentDO = validateContentExists(id);
            //消息只处理已发布的内容，其他状态设置置顶就等于预先设置
            contentDO.setTopLevel(topVo.getTopLevel());
            contentMapper.updateById(contentDO);
            if (EnumContentStatus.PUBLISHED.getStatus().equals(contentDO.getStatus())) {
                // 发布状态变更 触发相关事件
                //  比如状态已经发布 重新生成的静态页
                contentEventPublisher.sendContentCreateUpdateEvent(id, EnumContentEvent.update);
            }
        }
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_MOVE_SUB_TYPE, bizNo = "{{#moveVo.ids[0]}}", success = CMS_CONTENT_MOVE_BATCH_SUCCESS, extra = "{{#moveVo.ids}}")
    @Transactional(rollbackFor = Exception.class)
    public void batchMoveContent(ContentMoveVo moveVo) {
        ChannelDO channelDO = channelMapper.selectById(moveVo.getTargetChannelId());
        Assert.notNull(channelDO, () -> {
            throw exception(CHANNEL_NOT_EXISTS);
        });
        Map<Long, Long> contentOriginChannelIdMap = new HashMap<>();
        for (Long id : moveVo.getIds()) {
            // 校验存在
            ContentDO contentDO = validateContentExists(id);
            Long originChannelId = contentDO.getChannelId();
            contentDO.setChannelId(moveVo.getTargetChannelId());
            contentDO.setChannel(channelDO);
            contentMapper.updateById(contentDO);
            if (EnumContentStatus.PUBLISHED.getStatus().equals(contentDO.getStatus())) {
                // 发布栏目变更消息 触发相关事件
                //  重新生成的静态页，包含来源栏目和目标栏目
                contentOriginChannelIdMap.put(contentDO.getId(), originChannelId);
                RequestUtils.getHttpServletRequest().setAttribute(REQUEST_ATTR_CONTENT_ORIGIN_CHANNEL_ID_MAP, contentOriginChannelIdMap);
                contentEventPublisher.sendContentCreateUpdateEvent(id, EnumContentEvent.update);
            }
        }
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_MARK_SUB_TYPE, bizNo = "{{#markVo.ids[0]}}", success = CMS_CONTENT_MARK_BATCH_SUCCESS, extra = "{{#markVo.ids}}")
    @Transactional(rollbackFor = Exception.class)
    public void batchMarkContent(ContentMarkVo markVo) {
        for (Long id : markVo.getIds()) {
            // 校验存在
            ContentDO contentDO = validateContentExists(id);
            contentDO.setDictMark(markVo.getDictMark());
            contentMapper.updateById(contentDO);
            if (EnumContentStatus.PUBLISHED.getStatus().equals(contentDO.getStatus())) {
                // 发布列表页需要重新生成消息 触发相关事件
                //  重新生成栏目列表静态页
                contentEventPublisher.sendContentCreateUpdateEvent(id, EnumContentEvent.update);
            }
        }
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_BLOCK_SUB_TYPE, bizNo = "{{#blockVo.ids[0]}}", success = CMS_CONTENT_BLOCK_BATCH_SUCCESS, extra = "{{#blockVo.ids}}")
    @Transactional(rollbackFor = Exception.class)
    public void batchBlockContent(ContentBlockVo blockVo) {
        for (Long id : blockVo.getIds()) {
            // 校验存在
            ContentDO contentDO = validateContentExists(id);
            contentDO.setDictBlock(blockVo.getDictBlock());
            contentMapper.updateById(contentDO);
            if (EnumContentStatus.PUBLISHED.getStatus().equals(contentDO.getStatus())) {
                // 发布列表页需要重新生成消息 触发相关事件
                //  重新生成栏目列表静态页
                contentEventPublisher.sendContentCreateUpdateEvent(id, EnumContentEvent.update);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchCopyContent(ContentCopyVo copyVo, boolean submitWorkflow) {
        Long targetChannelId = copyVo.getTargetChannelId();
        ChannelDO targetChannel = channelMapper.selectById(targetChannelId);
        Assert.notNull(targetChannel, () -> {
            throw exception(CHANNEL_NOT_EXISTS);
        });
        SiteDO targetSite = null;
        if (copyVo.getTargetSiteId() != null) {
            targetSite = siteService.getSite(copyVo.getTargetSiteId());
            Assert.notNull(targetSite, () -> {
                throw exception(SITE_NOT_EXISTS);
            });
        }
        List<ContentDO> originContents = new ArrayList<>();
        for (Long id : copyVo.getIds()) {
            // 校验存在
            ContentDO contentDO = getContent(id);
            originContents.add(contentDO);
        }
        for (ContentDO content : originContents) {
            Long originContentId = content.getId();
            ContentDO originContent = content;
            content.setId(null);
            //推送复制 创建时间和创建人调整当期操作人
            content.setCreateTime(LocalDateTime.now());
            content.setCreator(SecurityFrameworkUtils.getLoginUserId().toString());
            if (EnumContentPushType.COPY.getType().equals(copyVo.getPushType())) {
                content.setCreateMode(EnumCreateMode.COPY.getMode());
            } else {
                content.setCreateMode(EnumCreateMode.QUOTE.getMode());
            }
            if (copyVo.getTargetStatus() != null) {
                content.setStatus(copyVo.getTargetStatus());
            }
            if (copyVo.getTargetSiteId() != null) {
                content.setSite(targetSite);
                content.setSiteId(copyVo.getTargetSiteId());
            }
            content.setChannelId(targetChannelId);
            content.setChannel(targetChannel);
            contentMapper.insert(content);
            long contentId = content.getId();
            //ext表
            ContentExtDO ext = content.getExt();
            ext.setId(contentId);
            contentExtMapper.insert(ext);
            //如果是推送到目标栏目存在工作流并且站点设置推送需要工作流审批 则提交审批
            if (submitWorkflow && StringUtils.isNotBlank(content.getChannel().getUseProcessDefinitionId())) {
                submit(new ContentSubmitVO(contentId, ""));
            }
            if (EnumContentPushType.COPY.getType().equals(copyVo.getPushType())) {
                //引用不复制自定义属性表和tag 计数表，支复制基础表和ext表数据
                //自定义属性
                content.getAttrDos().forEach(c -> {
                    c.setId(null);
                    c.setContentId(contentId);
                });
                contentAttrMapper.insertBatch(content.getAttrDos());
                //先保存tag词后再保存到内容表
                List<TagDO> tags = content.getTags();
                List<Long> tagIds = CollectionUtils.convertList(tags, TagDO::getId);
                List<ContentTagDo> contentTagDos = new ArrayList<>();
                tagIds.forEach(tagId -> {
                    ContentTagDo tagDo = new ContentTagDo(contentId, tagId);
                    contentTagDos.add(tagDo);
                });
                contentTagMapper.insertBatch(contentTagDos);
                //计数表
                contentCountMapper.insert(new ContentCountDO(contentId));
            }
            //复制产生新内容
            // 发布列表页需要重新生成消息 触发相关事件
            //  重新生成栏目列表静态页
            contentEventPublisher.sendContentCreateUpdateEvent(contentId, EnumContentEvent.create);
            //引用存入原文数据
            content.setOriginContentId(originContentId);
            content.setOriginContent(originContent);
            contentMapper.updateById(content);
            // 发布列表页需要重新生成消息 触发相关事件
            //  重新生成栏目列表静态页
            contentEventPublisher.sendContentCreateUpdateEvent(originContentId, EnumContentEvent.update);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void copyChannelContent(Long originChannelId, Long newChannelId, Long newSiteId) {
        final ContentPageReqVO reqVO = new ContentPageReqVO();
        reqVO.setPageNo(1);
        reqVO.setPageSize(100);
        reqVO.setChannelIds(Arrays.asList(originChannelId));
        //默认回复时间降序，ID降序排序
        Collection<OrderItem> orderItems = getOrderItems(Arrays.asList("id_desc"));
        reqVO.setOrderItems(orderItems);
        doCopyChannelContents(reqVO, newChannelId, newSiteId);
    }

    private void doCopyChannelContents(ContentPageReqVO reqVO, Long newChannelId, Long newSiteId) {
        TenantContextHolder.setTenantId(CMS_TENANT_ID);
        IPage<ContentDO> contentDOIPage = contentMapper.selectPage(reqVO, reqVO.getOrderItems(), new Page<>(reqVO.getPageNo(), reqVO.getPageSize()));
        PageResult<ContentDO> contentPage = pageConverter.page(contentDOIPage);
        final List<Long> ids = CollectionUtils.convertList(contentPage.getList(), ContentDO::getId);
        ContentCopyVo copyVo = new ContentCopyVo();
        copyVo.setPushType(EnumContentPushType.COPY.getType());
        copyVo.setTargetChannelId(newChannelId);
        copyVo.setTargetSiteId(newSiteId);
        copyVo.setIds(ids);
        //这里是站点创建的时候复制内容 不走审批
        batchCopyContent(copyVo, false);
        while (contentPage.getHasNext()) {
            reqVO.setPageNo(contentPage.getCurrent().intValue() + 1);
            doCopyChannelContents(reqVO, newChannelId, newSiteId);
        }
    }


    @Override
    public void batchPushContent(ContentPushVo pushVo) {
        SiteDO targetSite = siteService.getSite(pushVo.getTargetSiteId());
        Assert.notNull(targetSite, () -> {
            throw exception(SITE_NOT_EXISTS);
        });
        //目标站点不允许推送
        if (!targetSite.getSiteConfig().getBase().getAllowPush()) {
            throw exception(CONTENT_TARGET_SITE_NOT_ALLOW_PUSH);
        }
        //推送密码错误
        if (!pushVo.getPushPass().equals(targetSite.getSiteConfig().getBase().getPushPass())) {
            throw exception(CONTENT_TARGET_SITE_PUSH_PASS_ERROR);
        }
        ContentCopyVo contentCopyVo = ContentConvert.INSTANCE.convertPushToCopyVo(pushVo);
        batchCopyContent(contentCopyVo, targetSite.getPushSubmitWorkflow());
    }

    @Override
    public void autoPushContent(ContentAutoPushDto pushVo) {
        Long targetChannelId = pushVo.getTargetChannelId();
        ContentCopyVo contentCopyVo = ContentConvert.INSTANCE.convertPushDefToCopyVo(pushVo);
        ChannelDO channelDO = channelMapper.selectById(targetChannelId);
        if (channelDO != null) {
            contentCopyVo.setTargetSiteId(channelDO.getSiteId());
            //自动采集不走审批
            batchCopyContent(contentCopyVo, false);
        }
    }

    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_PUBLISH_SUB_TYPE, bizNo = "{{#id}}", success = CMS_CONTENT_PUBLISH_SUCCESS)
    public void publish(Long id) {
        // 校验存在
        ContentDO contentDO = validateContentExists(id);
        //检查是否配置了工作流，有工作流则不能直接发布
        AssertUtil.isNotBlank(contentDO.getChannel().getUseProcessDefinitionId(), () -> {
            throw exception(CONTENT_WORKFLOW_EXISTS);
        });
        //发布只处理非流转中的
        if (!EnumContentStatus.AUDITING.equals(contentDO.getStatus())) {
            contentDO.setStatus(EnumContentStatus.PUBLISHED.getStatus());
            contentMapper.updateById(contentDO);
            // 发布状态变更 触发相关事件
            // 比如状态已经发布 索引  重新生成的静态页
            contentEventPublisher.sendContentCreateUpdateEvent(id, EnumContentEvent.update);
        }
        // 记录操作日志上下文
        LogRecordContext.putVariable("content", contentDO);
    }

    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_OFFLINE_SUB_TYPE, bizNo = "{{#id}}", success = CMS_CONTENT_OFFLINE_SUCCESS)
    public void offline(Long id) {
        // 校验存在
        ContentDO contentDO = validateContentExists(id);
        //发布只处理已发布的
        if (EnumContentStatus.PUBLISHED.getStatus().equals(contentDO.getStatus())) {
            contentDO.setStatus(EnumContentStatus.OFFLINE.getStatus());
            contentMapper.updateById(contentDO);
            // 发布状态变更 触发相关事件
            // 比如状态已经发布 索引  重新生成的静态页
            contentEventPublisher.sendContentCreateUpdateEvent(id, EnumContentEvent.update);
        }
        // 记录操作日志上下文
        LogRecordContext.putVariable("content", contentDO);
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_PUBLISH_SUB_TYPE, bizNo = "{{#ids[0]}}", success = CMS_CONTENT_PUBLISH_BATCH_SUCCESS, extra = "{{#ids}}")
    @Transactional(rollbackFor = Exception.class)
    public void batchPublish(List<Long> ids) {
        for (Long id : ids) {
            publish(id);
        }
    }

    @Override
    @LogRecord(type = CMS_CONTENT_TYPE, subType = CMS_CONTENT_OFFLINE_SUB_TYPE, bizNo = "{{#ids[0]}}", success = CMS_CONTENT_OFFLINE_BATCH_SUCCESS, extra = "{{#ids}}")
    @Transactional(rollbackFor = Exception.class)
    public void batchOffline(List<Long> ids) {
        for (Long id : ids) {
            offline(id);
        }
    }

    private ContentDO validateContentExists(Long id) {
        ContentDO contentDO = contentMapper.selectById(id);
        if (contentDO == null) {
            throw exception(CONTENT_NOT_EXISTS);
        }
        //检查是否本站数据
        if (!CmsContextUtil.getCurrSiteId().equals(contentDO.getSiteId())) {
            throw exception(CONTENT_NOT_CURR_SITE);
        }
        return contentDO;
    }

    @Override
    public ContentDO getContent(Long id) {
        return contentMapper.selectById(id);
    }

    @Override
    public List<ContentDO> getContentList(Collection<Long> ids, Integer status) {
        return contentMapper.selectByContentIds(ids, status);
    }

    @Override
    public List<ContentDO> getContentList(Collection<Long> ids) {
        List<ContentDO> contentDOS = new ArrayList<>();
        for (Long id : ids) {
            contentDOS.add(getContent(id));
        }
        return contentDOS;
    }

    @Override
    public PageResult<ContentDO> getContentPage(ContentPageReqVO pageReqVO) {
        pageReqVO.setLivePreview(EnumIntCommonStatus.CLOSE.getStatus());
        ContentPermissionRespDTO contentPermission = permissionService.getContentPermission();
        //数据权限范围
        DeptDataPermissionRespDTO deptPermission = permissionService.getDeptPermission();
        //null 没有权限
        if (contentPermission == null || deptPermission == null) {
            return new PageResult<>();
        }
        //没有配置内容权限则直接返回
        if (!contentPermission.getAll() && CollUtil.isEmpty(contentPermission.getChannelIds())) {
            return new PageResult<>();
        }
        //限定了权限范围
        if (!deptPermission.getAll()) {
            // 即不能查看部门，又不能查看自己，则说明 100% 无权限
            if (CollUtil.isEmpty(deptPermission.getDeptIds())
                    && Boolean.FALSE.equals(deptPermission.getSelf())) {
                return new PageResult<>();
            }
        }

        List<Long> queryChannelIds = new ArrayList<>();
        Long parentChannelId = pageReqVO.getChannelId();
        if (parentChannelId != null) {
            ChannelDO channelDO = channelMapper.selectById(parentChannelId);
            if (channelDO != null) {
                List<ChannelDO> children = channelDO.getChildren();
                queryChannelIds.addAll(CollectionUtils.convertList(children, ChannelDO::getId));
            }
            queryChannelIds.add(parentChannelId);
        }
        if (CollUtil.isNotEmpty(pageReqVO.getChannelIds())) {
            queryChannelIds.addAll(pageReqVO.getChannelIds());
        }

        //限定部分栏目 对于已传入的查询栏目  则取差集
        //限定部分栏目 对于未传入的查询栏目  则查询的栏目是限定的栏目
        if (!contentPermission.getAll() && CollUtil.isNotEmpty(contentPermission.getChannelIds())) {
            if (CollUtil.isEmpty(queryChannelIds)) {
                queryChannelIds.addAll(contentPermission.getChannelIds());
            } else {
                Collection<Long> intersection = CollUtil.intersection(queryChannelIds, contentPermission.getChannelIds());
                queryChannelIds.clear();
                queryChannelIds.addAll(intersection);
            }
        }
        //作为最终的栏目参数
        pageReqVO.setChannelIds(queryChannelIds);

        //限定了数据范围 拼接 Dept 条件
        if (!deptPermission.getAll()) {
            pageReqVO.setDeptIds(deptPermission.getDeptIds());
        }
        //限定了数据范围 且只能查看自身权限 拼接 user 条件
        if (!deptPermission.getAll() && deptPermission.getSelf()) {
            pageReqVO.setUserId(SecurityFrameworkUtils.getLoginUserId());
        }
        //前端构造但条件查询更合适
        if (pageReqVO.getCreateTime() != null) {
            if (pageReqVO.getCreateTime().length > 0 && pageReqVO.getCreateTime()[0] != null) {
                pageReqVO.setCreateTimeBegin(pageReqVO.getCreateTime()[0]);
            }
            if (pageReqVO.getCreateTime().length > 1 && pageReqVO.getCreateTime()[1] != null) {
                pageReqVO.setCreateTimeEnd(pageReqVO.getCreateTime()[1]);
            }
        }
        if (pageReqVO.getReleaseTime() != null) {
            if (pageReqVO.getReleaseTime().length > 0 && pageReqVO.getReleaseTime()[0] != null) {
                pageReqVO.setReleaseTimeStart(pageReqVO.getReleaseTime()[0]);
            }
            if (pageReqVO.getReleaseTime().length > 1 && pageReqVO.getReleaseTime()[1] != null) {
                pageReqVO.setReleaseTimeEnd(pageReqVO.getReleaseTime()[1]);
            }
        }
        Collection<String> orderStrs = pageReqVO.getOrderStrs();
        if (CollUtil.isEmpty(orderStrs)) {
            //默认置顶优先，发布时间降序排序 id 降序
            orderStrs = StrUtils.splitToString("top_level_desc,release_time_desc,id_desc");
        }
        pageReqVO.setOrderItems(getOrderItems(orderStrs));
        IPage<ContentDO> contentDOIPage = contentMapper.selectPage(pageReqVO, pageReqVO.getOrderItems(), new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()));
        return pageConverter.page(contentDOIPage);
    }

    @Override
    public List<ContentDO> getContentList(ContentStatQueryReqVO reqVO) {
        reqVO.setLivePreview(EnumIntCommonStatus.CLOSE.getStatus());
        return PageMethod.offsetPage(reqVO.getOffset(), reqVO.getCount(), false).doSelectPage(() -> contentMapper.selectListStat(reqVO));
    }

    @Override
    public PageResult<ContentDO> getContentPageIgnorePerm(ContentPageReqVO pageReqVO) {
        pageReqVO.setLivePreview(EnumIntCommonStatus.CLOSE.getStatus());
        List<Long> queryChannelIds = new ArrayList<>();
        Long parentChannelId = pageReqVO.getChannelId();
        if (parentChannelId != null) {
            ChannelDO channelDO = channelMapper.selectById(parentChannelId);
            if (channelDO != null) {
                List<ChannelDO> children = channelDO.getChildren();
                queryChannelIds.addAll(CollectionUtils.convertList(children, ChannelDO::getId));
            }
            queryChannelIds.add(parentChannelId);
        }
        if (CollUtil.isNotEmpty(pageReqVO.getChannelIds())) {
            queryChannelIds.addAll(pageReqVO.getChannelIds());
        }
        //作为最终的栏目参数
        pageReqVO.setChannelIds(queryChannelIds);
        //前端构造但条件查询更合适
        if (pageReqVO.getCreateTime() != null) {
            if (pageReqVO.getCreateTime().length > 0 && pageReqVO.getCreateTime()[0] != null) {
                pageReqVO.setCreateTimeBegin(pageReqVO.getCreateTime()[0]);
            }
            if (pageReqVO.getCreateTime().length > 1 && pageReqVO.getCreateTime()[1] != null) {
                pageReqVO.setCreateTimeEnd(pageReqVO.getCreateTime()[1]);
            }
        }
        if (pageReqVO.getReleaseTime() != null) {
            if (pageReqVO.getReleaseTime().length > 0 && pageReqVO.getReleaseTime()[0] != null) {
                pageReqVO.setReleaseTimeStart(pageReqVO.getReleaseTime()[0]);
            }
            if (pageReqVO.getReleaseTime().length > 1 && pageReqVO.getReleaseTime()[1] != null) {
                pageReqVO.setReleaseTimeEnd(pageReqVO.getReleaseTime()[1]);
            }
        }
        Collection<String> orderStrs = pageReqVO.getOrderStrs();
        if (CollUtil.isEmpty(orderStrs)) {
            //默认浏览量降序，发布时间降序排序
            orderStrs = StrUtils.splitToString("count.views_desc,release_time_desc");
        }
        pageReqVO.setOrderItems(getOrderItems(orderStrs));
        pageReqVO.setJoinTables(getJoinTables(pageReqVO.getOrderItems()));
        IPage<ContentDO> contentDOIPage = contentMapper.selectPage(pageReqVO, pageReqVO.getOrderItems(), new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()));
        return pageConverter.page(contentDOIPage);
    }

    @Override
    public PageResult<ContentDO> getContentPageFront(ContentFrontQueryReqVO pageReqVO) {
        pageReqVO.setLivePreview(EnumIntCommonStatus.CLOSE.getStatus());
        IPage<ContentDO> contentDOIPage = contentMapper.selectPage(pageReqVO, pageReqVO.getOrderItems(), new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()));
        return pageConverter.page(contentDOIPage);
    }

    @Override
    public List<ContentDO> getContentListFront(ContentFrontQueryReqVO reqVO) {
        reqVO.setLivePreview(EnumIntCommonStatus.CLOSE.getStatus());
        return PageMethod.offsetPage(reqVO.getOffset(), reqVO.getCount(), false).doSelectPage(() -> contentMapper.selectList(reqVO, reqVO.getOrderItems()));
    }

    @Override
    public PageResult<ContentDO> getTodoContentPage(PageParam pageReqVO) {
        CommonResult<PageResult<Long>> todoContentIdPage = bpmTaskApi.getTodoContentIdPage(pageReqVO);
        if (todoContentIdPage.isError()) {
            throw exception(COMMON_RPC_BMP_ERROR);
        }
        PageResult<Long> todoContentIdPageData = todoContentIdPage.getData();
        if (todoContentIdPageData.getTotal() <= 0) {
            return new PageResult<>(Collections.emptyList(), 0L);
        }
        List<ContentDO> contentList = getContentList(todoContentIdPageData.getList(), EnumContentStatus.AUDITING.getStatus());
        return new PageResult<>(contentList, (long) contentList.size());
    }

    @Override
    public Long getTodoContentCount() {
        if (EnvUtil.getGrade() < 1) {
            return 0L;
        }
        CommonResult<PageResult<Long>> todoContentIdPage = bpmTaskApi.getTodoContentIdPage(new PageParam(1, 10));
        if (todoContentIdPage.isError()) {
            throw exception(COMMON_RPC_BMP_ERROR);
        }
        PageResult<Long> todoContentIdPageData = todoContentIdPage.getData();
        return todoContentIdPageData.getTotal();
    }

    @Override
    public ContentDO findSide(ContentSideReqVO sideReqVO, boolean next) {
        List<ContentDO> contentDOS = next ? PageMethod.offsetPage(0, 1, false).doSelectPage(() -> contentMapper.selectNext(sideReqVO)) :
                PageMethod.offsetPage(0, 1, false).doSelectPage(() -> contentMapper.selectPrev(sideReqVO));
        if (CollUtil.isEmpty(contentDOS)) {
            return null;
        }
        return contentDOS.get(0);
    }

    @Override
    public List<ContentDO> getListByMinId(Long minId, Long siteId, Integer status, int offset, int limit) {
        return PageMethod.offsetPage(offset, limit, false).doSelectPage(() -> contentMapper.getListByMinId(minId, siteId, status));
    }

    private void saveContentSubChannels(Long contentId, Set<Long> subChannelIds) {
        if (contentId == null || CollUtil.isEmpty(subChannelIds)) {
            return;
        }
        LambdaQueryWrapper<ContentSubChannelDO> queryWrapper = Wrappers.lambdaQuery(ContentSubChannelDO.class);
        queryWrapper.eq(ContentSubChannelDO::getContentId, contentId);
        contentSubChannelMapper.delete(queryWrapper);
        List<ContentSubChannelDO> subChannelDOS = new ArrayList<>();
        for (Long channelId : subChannelIds) {
            subChannelDOS.add(new ContentSubChannelDO(contentId, channelId));
        }
        contentSubChannelMapper.insertBatch(subChannelDOS);
    }

    public static Collection<JoinTable> getJoinTables(Collection<OrderItem> orderItems) {
        Set<JoinTable> joinTables = new HashSet<>();
        if (CollUtil.isNotEmpty(orderItems)) {
            final List<OrderItem> orderItemList = orderItems.stream().filter(c -> StringUtils.isNotBlank(c.getColumn())).collect(Collectors.toList());
            for (OrderItem orderItem : orderItemList) {
                String column = orderItem.getColumn();
                if (column.contains(ORDER_TABLE_CONTENT_COUNT)) {
                    joinTables.add(new JoinTable("cms_content_count", "count", "id", "id"));
                }
            }
        }
        return joinTables;
    }

    @Resource
    protected ContentMapper contentMapper;
    @Resource
    private ModelMapper modelMapper;

    @Resource
    private ContentSubChannelMapper contentSubChannelMapper;
    @Resource
    private ContentAttrMapper contentAttrMapper;
    @Resource
    private ContentExtMapper contentExtMapper;
    @Resource
    private ContentCountMapper contentCountMapper;
    @Resource
    private ChannelMapper channelMapper;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private ContentAttrService contentAttrService;
    @Resource
    protected PageConverter pageConverter;
    @Resource
    private PermissionServiceImpl permissionService;
    @Resource
    private TagService tagService;
    @Resource
    private DeptApi deptApi;
    @Resource
    private BpmTaskApi bpmTaskApi;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;

    @Resource
    private ContentTagMapper contentTagMapper;

    @Resource
    private SiteService siteService;

    @Resource
    private ContentJobService contentJobService;

    @Resource
    private ContentEventPublisher contentEventPublisher;
    @Resource
    private SensitiveWordApi sensitiveWordApi;
    @Resource
    private GlobalConfigService globalConfigService;

    @Lazy
    @Resource
    private SensitiveWordService sensitiveWordService;

}
